using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using HCore.Utility;
using UnityEngine;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;

namespace Game.Tile
{
    public class Tile
    {
        /// <summary>
        ///     贝塞尔曲线所在碎片方向
        /// </summary>
        public enum Direction
        {
            Up,
            Down,
            Left,
            Right
        }

        /// <summary>
        ///     贝塞尔曲线本身类型
        /// </summary>
        public enum PosNegType
        {
            /// <summary>
            ///     正向
            /// </summary>
            Pos,

            /// <summary>
            ///     逆向
            /// </summary>
            Neg,

            /// <summary>
            ///     无曲线
            /// </summary>
            None
        }

        #region 本身的位置、长宽、纹理、主题色等

        /// <summary>
        ///     归零的颜色
        /// </summary>
        private static readonly Color _TRANSPARENT_COLOR = new(0.0f, 0.0f, 0.0f, 0.0f);

        // /// <summary>
        // /// 主题色
        // /// </summary>
        // private Color mThemeColor;

        /// <summary>
        ///     我们将添加一个名为填充的变量。此变量表示用于定位曲线的偏移量。
        ///     这是一个整数值，它决定了曲线从默认位置移位多少。
        ///     对于140乘140像素的图像大小，填充物将为20。
        ///     <para>-------------------------------------</para>
        ///     The offset at which the curve will start.
        ///     For an image of size 140 by 140 it will start at 20, 20.
        ///     public Vector2Int mOffset = new Vector2Int(20, 20);
        /// </summary>
        public int Padding { get; }

        /// <summary>
        ///     我们添加名为 type integer 的 tileSize 的变量。
        ///     这个变量定义了 Tile 的大小。
        ///     它确定了用于与曲线定位和缩放相关的计算的瓷砖的尺寸。
        ///     <para>-------------------------------------</para>
        ///     The size of our jigsaw tile.
        /// </summary>
        public int TileSize { get; }

        /// <summary>
        ///     最终纹理
        ///     <para>
        ///         我们将创建一个名为 FinalCutTexture 的新纹理，并更改这个新纹理。
        ///         此 FinalCutTexture 属性允许用户访问最终纹理。请注意，我们只提供对此属性的访问权限。这意味着我们无法在该类之外修改 FinalCut 纹理。
        ///     </para>
        /// </summary>
        public Texture2D FinalCutTexture { get; }

        /// <summary>
        ///     从构造函数获取一个能够获取原始纹理的函数，我们通过此函数获取到的纹理来构建自己的 FinalCutTexture 纹理
        /// </summary>
        private Func<Texture2D> GetOriginalTexture { get; }

        public Tile(int tileSize, int padding, int x, int y, Func<Texture2D> originalTextureFunc)
        {
            GetOriginalTexture = originalTextureFunc;
            TileSize = tileSize;
            Padding = padding;
            X = x;
            Y = y;

            // 首先，我们将tileSizeWithPadding计算为填充物加瓷砖大小的两倍。
            //int padding = mOffset.x;
            var tileSizeWithPadding = 2 * Padding + TileSize;

            FinalCutTexture = new Texture2D(
                tileSizeWithPadding,
                tileSizeWithPadding,
                TextureFormat.ARGB32,
                false);

            // 接下来，它初始化名为finalCut的Texture2D对象，尺寸为tileSizeWithPadding，并格式化TextureFormat.ARGB32。随后，它遍及finalCut的每个像素，并将其颜色设置为TransparentColor。
            var enumerable = Enumerable.Repeat(_TRANSPARENT_COLOR, tileSizeWithPadding * tileSizeWithPadding);
            FinalCutTexture.SetPixels(enumerable.ToArray());
        }

        #endregion

        #region 贝塞尔曲线的生成

        /// <summary>
        ///     <para>保存当前 Tile 四边的曲线类型</para>
        ///     其中每个元素对应于方向枚举指定的方向。最初，所有元素都设置为 PosNegType.NONE 。
        ///     然后，我们实现 SetCurveType 方法，允许通过接受方向枚举和 PosNegType 枚举并将数组中的相应元素设置为提供的类型来修改特定方向的曲线类型。
        ///     以及，我们还编写了 GetCurveType 方法，通过使用提供的方向枚举索引到他们 mCurveTypes 数组并返回存储的 PosNegType 来检索给定方向的曲线类型。
        /// </summary>
        private readonly PosNegType[] mCurveTypes =
        {
            // Up       0
            PosNegType.None,
            // Down     1
            PosNegType.None,
            // Left     2
            PosNegType.None,
            // Right    3
            PosNegType.None
        };

        /// <summary>
        ///     通过接受方向枚举和 PosNegType 枚举并将数组中的相应元素设置为提供的类型来修改特定方向的曲线类型。
        /// </summary>
        /// <param name="dir"></param>
        /// <param name="type"></param>
        public void SetCurveType(Direction dir, PosNegType type)
        {
            mCurveTypes[(int)dir] = type;
        }

        /// <summary>
        ///     通过使用提供的方向枚举索引到他们 mCurveTypes 数组并返回存储的 PosNegType 来检索给定方向的曲线类型。
        /// </summary>
        /// <param name="dir"></param>
        /// <returns></returns>
        public PosNegType GetCurveType(Direction dir)
        {
            return mCurveTypes[(int)dir];
        }

        /// <summary>
        ///     缓存的贝塞尔曲线点。根据拼图块的大小来进行缓存
        /// </summary>
        private static readonly Dictionary<int, List<Vector2>> _TITLE_SIZE_2_BEZIER_CURVES = new();

        /// <summary>
        ///     构建贝塞尔曲线 。
        ///     该返回包含定义贝塞尔曲线的 Vector2 点列表。
        ///     它使用 BezierCurves.GenerateBezierCurves 方法从贝塞尔曲线计算中生成的点进行初始化，该方法将模板控制点作为输入。
        ///     BezCurve 表示预先计算的贝塞尔曲线，该曲线将用作模板，用于在瓷砖类中根据不同参数（如方向和类型）生成实际曲线。
        ///     <para>这里使用了缓存，相同 TileSize 会返回相同的曲线点数组，会节约大量构建时间。</para>
        /// </summary>
        private List<Vector2> BuildBezierCurves()
        {
            // 先从缓存获取贝塞尔曲线点
            if (_TITLE_SIZE_2_BEZIER_CURVES.TryGetValue(TileSize, out var bezierCurves)) return bezierCurves;

            // 获取[组成贝塞尔曲线点的数量]，默认200个（对应100的拼图块宽高）
            var bezierPointCount = 50 + TileSize * 8; // 100个拼图块宽高对应200个数量。所以，如果宽高变化，则需要重新计算一下。
            bezierPointCount = Math.Max(220, bezierPointCount);
            var templateControlPoints = GetTemplateControlPoints(TileSize, Padding);
            bezierCurves = BezierCurves.GenerateBezierCurves(templateControlPoints, bezierPointCount);
            _TITLE_SIZE_2_BEZIER_CURVES.Add(TileSize, bezierCurves);
            return bezierCurves;
        }

        /// <summary>
        ///     各种贝塞尔曲线缓存
        /// </summary>
        private static readonly Dictionary<(int, Direction dir, PosNegType type), List<Vector2>> _CURVE_DICTIONARY = new();

        /// <summary>
        ///     方法将负责通过转换点，从模板贝塞尔曲线中创建各种类型的曲线。
        ///     转换将由我们刚刚实现的上述三个实用函数完成。
        ///     在该方法中，我们通过缓存偏移x和y以及瓷砖大小来初始化一些局部变量。
        ///     然后，我们将模板 Bézier 复制到一个新的列表中。
        ///     再然后，我们将根据曲线的方向和类型将我们的变换应用于这些点。
        ///     最后，该方法根据方向参数输入开关案例语句。
        /// </summary>
        /// <param name="dir"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentOutOfRangeException"></exception>
        private List<Vector2> CreateCurve(Direction dir, PosNegType type)
        {
            if (_CURVE_DICTIONARY.TryGetValue((TileSize, dir, type), out var curve)) return curve;

            // Debug.Log($"TileSize: {TileSize},dir: {dir},type: {type}");

            var paddingX = Padding; // mOffset.x;
            var paddingY = Padding; // mOffset.y;
            var tileSizeWidth = TileSize;
            var tileSizeHeight = TileSize;

            var pts = new List<Vector2>(BuildBezierCurves());
            switch (dir)
            {
                case Direction.Up:
                    switch (type)
                    {
                        // 如果类型是 POS，它通过将 paddingX 添加到x坐标和 paddingY +瓷砖大小 到 y 坐标来转换点。
                        case PosNegType.Pos:
                            TranslateXY(pts, new Vector2(paddingX, paddingY + tileSizeHeight));
                            break;
                        // 如果类型是NEG，它会反转y轴上的点，然后像以前一样转换它。
                        case PosNegType.Neg:
                            InvertY(pts);
                            TranslateXY(pts, new Vector2(paddingX, paddingY + tileSizeHeight));
                            break;
                        // 如果类型为NONE，画直线，它会清除pts，并通过在x轴上从偏移x加99和在y轴上偏移y加tileSize添加点来创建直线。
                        case PosNegType.None:
                            pts.Clear();
                            for (var i = 0; i < TileSize; ++i) pts.Add(new Vector2(i + paddingX, paddingY + tileSizeHeight));
                            break;
                        default:
                            throw new ArgumentOutOfRangeException(nameof(type), type, null);
                    }

                    break;
                case Direction.Right:
                    switch (type)
                    {
                        case PosNegType.Pos:
                            SwapXY(pts);
                            TranslateXY(pts, new Vector2(paddingX + tileSizeWidth, paddingY));
                            break;
                        case PosNegType.Neg:
                            InvertY(pts);
                            SwapXY(pts);
                            TranslateXY(pts, new Vector2(paddingX + tileSizeWidth, paddingY));
                            break;
                        case PosNegType.None:
                            pts.Clear();
                            for (var i = 0; i < TileSize; ++i) pts.Add(new Vector2(paddingX + tileSizeWidth, i + paddingY));
                            break;
                        default:
                            throw new ArgumentOutOfRangeException(nameof(type), type, null);
                    }

                    break;
                case Direction.Down:
                    switch (type)
                    {
                        case PosNegType.Pos:
                            InvertY(pts);
                            TranslateXY(pts, new Vector2(paddingX, paddingY));
                            break;
                        case PosNegType.Neg:
                            TranslateXY(pts, new Vector2(paddingX, paddingY));
                            break;
                        case PosNegType.None:
                            pts.Clear();
                            for (var i = 0; i < TileSize; ++i) pts.Add(new Vector2(i + paddingX, paddingY));
                            break;
                        default:
                            throw new ArgumentOutOfRangeException(nameof(type), type, null);
                    }

                    break;
                case Direction.Left:
                    switch (type)
                    {
                        case PosNegType.Pos:
                            InvertY(pts);
                            SwapXY(pts);
                            TranslateXY(pts, new Vector2(paddingX, paddingY));
                            break;
                        case PosNegType.Neg:
                            SwapXY(pts);
                            TranslateXY(pts, new Vector2(paddingX, paddingY));
                            break;
                        case PosNegType.None:
                            pts.Clear();
                            for (var i = 0; i < TileSize; ++i) pts.Add(new Vector2(paddingX, i + paddingY));
                            break;
                        default:
                            throw new ArgumentOutOfRangeException(nameof(type), type, null);
                    }

                    break;
                default:
                    throw new ArgumentOutOfRangeException(nameof(dir), dir, null);
            }

            _CURVE_DICTIONARY.Add((TileSize, dir, type), pts);
            return pts;
        }

        #region 贝塞尔曲线控制点操作函数

        /// <summary>
        ///     移动 XY 轴
        /// </summary>
        /// <param name="controlPoints">控制点</param>
        /// <param name="offset">偏移量</param>
        private static void TranslateXY(List<Vector2> controlPoints, Vector2 offset)
        {
            for (var i = 0; i < controlPoints.Count; i++) controlPoints[i] += offset;
        }

        /// <summary>
        ///     翻转 Y 轴
        /// </summary>
        /// <param name="controlPoints"></param>
        private static void InvertY(List<Vector2> controlPoints)
        {
            for (var i = 0; i < controlPoints.Count; i++) controlPoints[i] = new Vector2(controlPoints[i].x, -controlPoints[i].y);
        }

        /// <summary>
        ///     替换 XY 轴
        /// </summary>
        /// <param name="controlPoints"></param>
        private static void SwapXY(List<Vector2> controlPoints)
        {
            for (var i = 0; i < controlPoints.Count; ++i) controlPoints[i] = new Vector2(controlPoints[i].y, controlPoints[i].x);
        }

        #endregion

        #endregion

        #region 拼图边缘的贝塞尔曲线模板数据

        private static readonly List<Vector2> _TEMPLATE_CONTROL_POINTS = new()
        {
            new Vector2(0, 0),
            new Vector2(35, 15),
            new Vector2(47, 13),
            new Vector2(45, 5),
            new Vector2(48, 0),
            new Vector2(25, -5),
            new Vector2(15, -18),
            new Vector2(36, -20),
            new Vector2(64, -20),
            new Vector2(85, -18),
            new Vector2(75, -5),
            new Vector2(52, 0),
            new Vector2(55, 5),
            new Vector2(53, 13),
            new Vector2(65, 15),
            new Vector2(100, 0)
        };

        /// <summary>
        ///     贝塞尔曲线模板的所有控制点
        ///     <para>会自动根据拼图块的大小，来放大缩小贝塞尔曲线，从而计算出所有的准确控制点</para>
        /// </summary>
        /// <param name="tileSize"></param>
        /// <param name="padding"></param>
        /// <returns></returns>
        public static List<Vector2> GetTemplateControlPoints(int tileSize, int padding)
        {
            // 默认的贝塞尔曲线长为100，高为35。所以按照这个比例进行缩放
            return _TEMPLATE_CONTROL_POINTS.Select(point => new Vector2(point.x * tileSize / 100, point.y * padding / 35)).ToList();
        }

        #endregion

        #region 洪水算法填充新的拼图块

        // 在整个拼图2d数的两个下标变量
        public int X { get; }
        public int Y { get; }
        public Vector2Int Index => new(X, Y);

        /// <summary>
        ///     这是根据曲线切割图像瓷砖并修改纹理 FinalCut 的主要方法。
        ///     我们通过调用两个尚未实现的函数来做到这一点（洪水填充算法：洪水填充算法是计算机图形学中用于确定和更改光栅图像或位图中连接区域颜色的技术。）。
        ///     第一个是 FloodFillInit，第二个是 FloodFill。
        ///     这两个功能将对图像 FinalCut 进行所有必要的更改。
        ///     为了完成 FinalCut 图像的更改，我们调用 FinalCut.Apply 方法。
        ///     <para>-------------------------------------</para>
        ///     <ul>
        ///         我们将使用名为 FloodFillInit 的方法执行步骤1，并使用 FloodFill 方法执行 步骤2 和 步骤3。
        ///         <li> 步骤1：根据曲线和直线设置纹理的边界。将属于这组点的所有像素标记为访问过。</li>
        ///         <li> 步骤2：取finalCut纹理的中心像素，并从输入纹理的中心像素中设置颜色值。将其标记为访问过。将此像素添加到堆栈中。</li>
        ///         <li> 步骤3：当像素堆栈不是空的时，向上、向左、向右和向下获取下一个像素。如果下一个像素已被标记为已访问，那么我们不会处理该像素。如果没有，那么我们将该像素设置为访问，从同一像素的原始纹理中设置颜色，并将其添加到堆栈中。</li>
        ///     </ul>
        /// </summary>
        public void Apply()
        {
            var stopwatchSum = Stopwatch.StartNew();

            // Debug.Log($"Tile {X}:{Y} Apply! ");
            var stopwatch = Stopwatch.StartNew();

            // 初始化洪水填充相关数据
            var visited = FloodFillInit();
            // Debug.Log($"Tile {X}:{Y} Apply!   -----   FloodFillInit: 耗时：{stopwatch.ElapsedMilliseconds}ms");
            stopwatch.Restart();

            // 执行洪水填充
            FloodFill(visited);
            // Debug.Log($"Tile {X}:{Y} Apply!   -----   FloodFill: 耗时：{stopwatch.ElapsedMilliseconds}ms, visited count: {visited.Length}");
            stopwatch.Restart();

            // <see cref="https://blog.csdn.net/weixin_42358083/article/details/126717806"/>
            // 创建或者更新纹理后，需要通过 Apply 函数，提交到 GPU 进行绘制。
            FinalCutTexture.Apply();
            // Debug.Log($"Tile {X}:{Y} Apply!   -----   Apply: 耗时：{stopwatch.ElapsedMilliseconds}ms");
            stopwatch.Stop();

            // Debug.Log($"Tile {X}:{Y} Apply!: 耗时：{stopwatchSum.ElapsedMilliseconds}ms, ThreadID: {Thread.CurrentThread.ManagedThreadId}");
            stopwatchSum.Stop();
        }

        /// <summary>
        ///     这种方法通过首先计算包含填充物的瓷砖尺寸来初始化洪水填充过程。
        ///     <ul>
        ///         <li>
        ///             首先，它初始化了2D布尔数组 visited ，其尺寸与瓷砖的大小相对应，确保可以跟踪每个像素的访问状态。
        ///             visited 的所有元素最初都设置为 false，以表示尚未访问过像素。
        ///         </li>
        ///         <li>
        ///             然后，该方法通过在 mCurveTypes 数组上进行遍历并使用 CreateCurve 方法为每个方向和类型创建曲线来生成表示瓷砖闭合曲线的点列表。
        ///             通过将 visited 数组中的相应元素设置为 true ，将曲线包围的像素标记为已访问，防止洪水填充算法试图再次填充它们。
        ///         </li>
        ///         <li>从本质上讲，该方法通过准备访问数组、标记曲线包围的像素和定义洪水填充操作的起点来设置洪水填充算法的初始状态。</li>
        ///     </ul>
        /// </summary>
        /// <returns>返回一个用作洪水填充的网格，用于存储有关在洪水填充等过程中是否访问过特定像素的信息。</returns>
        private bool[,] FloodFillInit()
        {
            // 构建一个 用作洪水填充的网格
            var tileSizeWithPadding = Padding + TileSize + Padding; // 47 +623 +47
            var visited = new bool[tileSizeWithPadding, tileSizeWithPadding];
            for (var i = 0; i < tileSizeWithPadding; ++i)
            for (var j = 0; j < tileSizeWithPadding; ++j)
                visited[i, j] = false;

            // 循环瓷砖闭合曲线的点列表，设置 true ，来标记已访问
            for (var i = 0; i < mCurveTypes.Length; ++i)
                CreateCurve((Direction)i, mCurveTypes[i]).ForEach(v => visited[(int)v.x, (int)v.y] = true);
            return visited;
        }

        /// <summary>
        ///     填充方法从指定位置的原始纹理中检索像素的颜色，将其不透明度调整为完全不透明，然后将finalCut纹理中的相应像素设置为此修改后的颜色。
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        private void Fill(int x, int y)
        {
            var texture = GetOriginalTexture();
            var c = texture.GetPixel(
                x + X * TileSize,
                y + Y * TileSize);
            c.a = 1.0f;
            FinalCutTexture.SetPixel(x, y, c);
        }

        /// <summary>
        ///     <ul>
        ///         <li>首先，从瓷砖中心初始化洪水填充过程，并将这个中心像素标记为已访问。中心像素的坐标被推到 stack 上，表明它是洪水填充操作的起点。</li>
        ///         <li>其次，它首先用填充和瓷砖尺寸值计算瓷砖的宽度和高度。</li>
        ///         <li>然后，它迭代处理存储在mStack中的像素，直到没有更多像素需要处理。对于从堆栈中弹出的每个像素，它使用我们之前实现的 Fill 实用程序方法填充 FinalCut 纹理中的相应像素。</li>
        ///         <li>
        ///             接下来，它检查四个方向的相邻像素：右、左、上和下。对于每个相邻的像素，它会验证它是否属于瓷砖的范围，是否尚未被访问。
        ///             如果满足这些条件，相邻的像素将被标记为已访问，其坐标将被推送到堆栈上进行进一步处理。
        ///         </li>
        ///         <li>这个过程继续进行，直到瓷砖中所有连接的像素都被填充，确保曲线包围的整个区域都着色。</li>
        ///     </ul>
        /// </summary>
        // TODO 其实这个算法有严重性能问题。之后可以想办法优化。
        private void FloodFill(bool[,] visited)
        {
            // 首先填充中心点，中心点一定是可填充的像素点。
            var tileSizeWithPadding = Padding + TileSize + Padding;
            var startVector = new Vector2Int(tileSizeWithPadding / 2, tileSizeWithPadding / 2);
            visited[startVector.x, startVector.y] = true;

            // 此堆栈用于存储洪水填充操作期间需要处理或访问的像素坐标。
            // 堆栈数据结构遵循 Last In, First Out 原则，这意味着添加到堆栈的最后一个元素将是第一个被删除的元素。
            // 在洪水填充的上下文中，堆栈用于跟踪需要用颜色填充或以其他方式处理的像素。
            var waitVectorStack = new Stack<Vector2Int>();
            waitVectorStack.Push(startVector);

            while (waitVectorStack.Count > 0)
            {
                var waitVector = waitVectorStack.Pop(); // 弹出一个等待处理的像素点
                var xx = waitVector.x;
                var yy = waitVector.y;

                Fill(waitVector.x, waitVector.y); // 填充 FinalCut 的像素

                ///// 以下开始侦测上下左右了

                // Check right.
                var x = xx + 1;
                var y = yy;

                if (x < tileSizeWithPadding) _SetVisited(x, y);

                // check left.
                x = xx - 1;
                y = yy;
                if (x > 0) _SetVisited(x, y);

                // Check up.
                x = xx;
                y = yy + 1;

                if (y < tileSizeWithPadding) _SetVisited(x, y);

                // Check down.
                x = xx;
                y = yy - 1;

                if (y >= 0) _SetVisited(x, y);
            }

            // yield break;

            void _SetVisited(int x, int y)
            {
                if (visited[x, y]) return;
                visited[x, y] = true;
                waitVectorStack.Push(new Vector2Int(x, y));
            }
        }

        #endregion

        #region 贝塞尔曲线绘制

        /// <summary>
        ///     我们添加mLineRenderers。
        ///     此变量是一个字典，根据方向和类型存储LineRenderer对象。
        ///     它将每个方向和类型组合与LineRenderer相关联，以便轻松检索和管理LineRenderers以绘制曲线。
        ///     <para>-------------------------------------</para>
        ///     The line renderers for all directions and types.
        /// </summary>
        private readonly Dictionary<(Direction, PosNegType), LineRenderer> mLineRenderers = new();

        /// <summary>
        ///     隐藏所有的贝塞尔曲线
        /// </summary>
        public void HideAllCurves()
        {
            foreach (var item in mLineRenderers) item.Value.gameObject.SetActive(false);
        }

        /// <summary>
        ///     销毁所有的贝塞尔曲线
        /// </summary>
        public void DestroyAllCurves()
        {
            foreach (var item in mLineRenderers) Object.Destroy(item.Value.gameObject);

            mLineRenderers.Clear();
        }

        /// <summary>
        ///     绘曲线
        ///     <para>根据提供的方向和类型生成表示曲线的点列表</para>
        /// </summary>
        /// <param name="dir">贝塞尔曲线所在碎片方向</param>
        /// <param name="type">贝塞尔曲线本身类型</param>
        /// <param name="color">曲线颜色</param>
        public void DrawCurve(Direction dir, PosNegType type, Color color)
        {
            // 它检查与给定方向和类型关联的LineRenderer是否存在于mLineRenderers字典中；
            // 如果没有，它使用我们之前实现的 CreateLineRenderer 函数创建具有指定颜色的新LineRenderer，
            // 并将其添加到字典中。
            if (!mLineRenderers.ContainsKey((dir, type))) mLineRenderers.Add((dir, type), CreateLineRenderer(color));

            // 如果有它从字典中检索 LineRenderer，并将其开始和结束颜色设置为提供的颜色。
            // 该函数还为 LineRenderer 的游戏对象分配了一个描述性名称，以在Unity编辑器中清晰明了。
            var lr = mLineRenderers[(dir, type)];
            lr.gameObject.SetActive(true);
            lr.startColor = color;
            lr.endColor = color;
            lr.gameObject.name = $"LineRenderer_{dir}_{type}";
            var pts = CreateCurve(dir, type);

            lr.positionCount = pts.Count;
            for (var i = 0; i < pts.Count; ++i) lr.SetPosition(i, pts[i]);
        }

        /// <summary>
        ///     我们添加CreateLineRenderer函数。
        ///     这是一种静态方法，旨在简化在Unity中生成LineRenderer组件的过程。
        ///     调用时，它应该实例化一个新的GameObject，作为LineRenderer的容器。
        ///     随后，它将向此GameObject添加一个LineRenderer组件，根据提供的参数配置其属性，如开始颜色、结束颜色、开始宽度和结束宽度。
        ///     此外，它还将为LineRenderer分配一个材质，这决定了如何呈现行；在这种情况下，它使用默认的精灵着色器。
        ///     完成所有配置后，该函数将返回创建的LineRenderer，允许在Unity场景中进一步定制或立即使用。
        /// </summary>
        /// <param name="color"></param>
        /// <param name="lineWidth"></param>
        /// <returns></returns>
        private static LineRenderer CreateLineRenderer(Color color, float lineWidth = 1.0f)
        {
            var obj = new GameObject();
            var lr = obj.AddComponent<LineRenderer>();

            lr.startColor = color;
            lr.endColor = color;
            lr.startWidth = lineWidth;
            lr.endWidth = lineWidth;
            lr.material = new Material(Shader.Find("Sprites/Default"));
            return lr;
        }

        #endregion
    }
}